package com.wemei.auto.ui.utils

import androidx.collection.LruCache
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType

object ReflectUtils {

    @JvmField
    val classFieldCache: LruCache<Class<*>, Map<String, Field>> = object : LruCache<Class<*>, Map<String, Field>>(64) {
        override fun create(clz: Class<*>): Map<String, Field> {
            val fields: MutableList<Field> = ArrayList()
            var superclass: Class<*>? = clz
            while (superclass != null && superclass != Any::class.java) {
                fields.addAll(superclass.declaredFields)
                superclass = superclass.superclass
            }
            val fieldMap: MutableMap<String, Field> = HashMap()
            for (field in fields) {
                if (!Modifier.isStatic(field.modifiers)) {
                    field.isAccessible = true
                    fieldMap[field.name] = field
                }
            }
            return fieldMap
        }
    }

    @JvmStatic
    private val unsafe: Any by lazy {
        val unsafeClass = Class.forName("sun.misc.Unsafe")
        val f = unsafeClass.getDeclaredField("theUnsafe")
        f.isAccessible = true
        f[null]
    }

    @JvmStatic
    fun <T> findParameterizedTypeClz(clz: Class<*>, index: Int): Class<T> {
        var genericSuperclass = clz.genericSuperclass
        while (genericSuperclass != null) {
            if (genericSuperclass is ParameterizedType) {
                break
            }
            genericSuperclass = (genericSuperclass as Class<*>).genericSuperclass
        }
        return (genericSuperclass as ParameterizedType).actualTypeArguments[index] as Class<T>
    }

    @JvmStatic
    fun <T> getStaticFieldObj(clz: Class<*>, fieldName: String): T {
        return clz.getDeclaredField(fieldName).let {
            it.isAccessible = true
            it.get(null)
        } as T
    }

    @JvmStatic
    fun <T> getFieldObj(obj: Any, fieldName: String): T {
        return classFieldCache.get(obj.javaClass)!![fieldName]!!.let {
            it.isAccessible = true
            it.get(obj)
        } as T
    }

    @JvmStatic
    fun <T : Any> fieldSetValue(source: T, target: Any, fieldName: String, getValue: ((source: T) -> Any)? = null) {
        val value = getValue?.invoke(source) ?: let {
            val sourceField = source.javaClass.getDeclaredField(fieldName)
            sourceField.isAccessible = true
            sourceField.get(source)
        }
        val targetField = target.javaClass.getDeclaredField(fieldName)
        targetField.isAccessible = true
        targetField.set(target, value)
    }

    @JvmStatic
    fun <T> getUnsafeStaticFieldObj(clz: Class<*>, fieldName: String): T {
        val field = clz.getDeclaredField(fieldName)
        return getObject(staticFieldBase(field), staticFieldOffset(field)) as T
    }

    @JvmStatic
    fun staticFieldBase(field: Field): Any? {
        return unsafe::class.java.getDeclaredMethod("staticFieldBase", Field::class.java).invoke(unsafe, field)
    }

    @JvmStatic
    fun staticFieldOffset(field: Field): Long? {
        return unsafe::class.java.getDeclaredMethod("staticFieldOffset", Field::class.java).invoke(unsafe, field) as? Long
    }

    @JvmStatic
    fun getObject(base: Any?, offset: Long?): Any? {
        return unsafe::class.java.getDeclaredMethod("getObject", Any::class.java, Long::class.java).invoke(unsafe, base, offset)
    }
}